#include <SDL/SDL.h>
#include <SDL/SDL_thread.h>
#include <fstream>
#include <iostream>
#include <mutex>
#include <queue>
#include <thread>

extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/imgutils.h>
#include <libswresample/swresample.h>
#include <libswscale/swscale.h>
}

#define MAX_AUDIO_FRAME_SIZE 192000

std::mutex mutex;
std::condition_variable waitCondition;
std::queue<AVPacket *> packets;
SwrContext *swrContext = nullptr;

int audioDecodeFrame(AVCodecContext *codecCtx, uint8_t *audioBuf, int bufSize) {
  int dataSize(-1);

  std::unique_lock<std::mutex> lock(mutex);
  if (packets.empty()) {
    waitCondition.wait(lock);
  }

  // 队列中取 packet
  AVPacket *packet = packets.front();
  packets.pop();
  lock.unlock();

  // packet 发送到解码器
  avcodec_send_packet(codecCtx, packet);

  // 获取解码后的帧，一个音频帧包含多个音频采样点
  AVFrame *frame = av_frame_alloc();
  while (avcodec_receive_frame(codecCtx, frame) == 0) {
    // 音频重采样，将音频数据转换为所需的格式（采样频率、声道数、样本位数）
    int out_nb_samples =
        swr_convert(swrContext, &audioBuf, frame->nb_samples,
                    (const uint8_t **)frame->data, frame->nb_samples);

    dataSize = av_samples_get_buffer_size(nullptr, 2, out_nb_samples,
                                          AV_SAMPLE_FMT_S16, 1);
  }

  av_frame_free(&frame);
  av_packet_free(&packet);
  return dataSize;
}

// 该回调函数负责往 stream 缓冲区存放 len 字节的数据
void audioCallback(void *userdata, Uint8 *stream, int len) {
  // 存放解码出音频数据
  static uint8_t audioBuf[MAX_AUDIO_FRAME_SIZE];
  static uint32_t audioBufSize = 0;
  static uint32_t audioBufIndex = 0;

  AVCodecContext *codecCtx = (AVCodecContext *)userdata;

  // 需要存放的字节数剩余
  int lenRemain = len;
  while (lenRemain > 0) {
    if (audioBufIndex >= audioBufSize) {
      // 解码出的音频数据为空（或已消耗完毕），则执行音频解码
      int audioSize = audioDecodeFrame(codecCtx, audioBuf, sizeof(audioBuf));
      if (audioSize < 0) {
        // 如果出错，填充数据 0，即无声音
        audioBufSize = 1024;
        std::memset(audioBuf, 0, audioBufSize);
      } else {
        audioBufSize = audioSize;
      }

      audioBufIndex = 0;
    }

    // 需要拷贝的音频数据大小
    int cpSize = audioBufSize - audioBufIndex;
    if (lenRemain < cpSize) {
      cpSize = lenRemain;
    }

    // 拷贝数据，并更新缓冲区相关信息
    std::memcpy(stream, (uint8_t *)audioBuf + audioBufIndex, cpSize);
    audioBufIndex += cpSize;
    stream += cpSize;
    lenRemain -= cpSize;
  }
}

// Playing Sound
int main(int argc, char *argv[]) {
  // 初始化SDL
  if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER) < 0) {
    std::cout << "error: Could not initialize SDL - " << SDL_GetError()
              << std::endl;
    return 0;
  }

  int ret(0);
  AVFormatContext *formatCtx(nullptr);
  AVCodecContext *videoCodecCtx(nullptr);
  AVCodecContext *audioCodecCtx(nullptr);
  AVFrame *frame(nullptr);
  SDL_Overlay *sdlOverlay(nullptr);

  // 取命令行参数获取文件名
  std::string filename;
  if (argc > 1) {
    filename = argv[1];
  } else {
    filename = "C:/Users/zhou/Videos/bigbuckbunny.mp4";
  }

  // 打开文件
  ret = avformat_open_input(&formatCtx, filename.c_str(), nullptr, nullptr);
  if (ret < 0) {
    std::cout << "error: avformat_open_input() failed" << std::endl;
    goto fail;
  }

  // 初始化流信息
  ret = avformat_find_stream_info(formatCtx, nullptr);
  if (ret < 0) {
    std::cout << "error: avformat_find_stream_info() failed" << std::endl;
    goto fail;
  }

  // 查找视频流、音频流
  int videoStream(-1);
  int audioStream(-1);
  for (uint32_t i = 0; i < formatCtx->nb_streams; ++i) {
    if (formatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO &&
        videoStream < 0) {
      videoStream = i;
    } else if (formatCtx->streams[i]->codecpar->codec_type ==
                   AVMEDIA_TYPE_AUDIO &&
               audioStream < 0) {
      audioStream = i;
    }
  }

  if (videoStream == -1) {
    std::cout << "error: Didn't find a video stream";
    goto fail;
  }

  if (audioStream == -1) {
    std::cout << "error: Didn't find a audio stream";
    goto fail;
  }

  // 获取视频解码器
  const AVCodec *videoCodec =
      avcodec_find_decoder(formatCtx->streams[videoStream]->codecpar->codec_id);
  if (!videoCodec) {
    std::cout << "error: Unsupported codec" << std::endl;
    goto fail;
  }

  // 创建解码器上下文
  videoCodecCtx = avcodec_alloc_context3(videoCodec);
  if (!videoCodecCtx) {
    std::cout << "error: avcodec_alloc_context3() failed" << std::endl;
    goto fail;
  }

  ret = avcodec_parameters_to_context(
      videoCodecCtx, formatCtx->streams[videoStream]->codecpar);
  if (ret < 0) {
    std::cout << "error: avcodec_parameters_to_context() failed" << std::endl;
    goto fail;
  }

  // 初始化解码器上下文
  ret = avcodec_open2(videoCodecCtx, videoCodec, nullptr);
  if (ret < 0) {
    std::cout << "error: avcodec_open2() failed" << std::endl;
    goto fail;
  }

  // 获取音频解码器
  const AVCodec *audioCodec =
      avcodec_find_decoder(formatCtx->streams[audioStream]->codecpar->codec_id);
  if (!audioCodec) {
    std::cout << "error: Unsupported codec" << std::endl;
    goto fail;
  }

  // 创建解码器上下文
  audioCodecCtx = avcodec_alloc_context3(audioCodec);
  if (!audioCodecCtx) {
    std::cout << "error: avcodec_alloc_context3() failed" << std::endl;
    goto fail;
  }

  ret = avcodec_parameters_to_context(
      audioCodecCtx, formatCtx->streams[audioStream]->codecpar);
  if (ret < 0) {
    std::cout << "error: avcodec_parameters_to_context() failed" << std::endl;
    goto fail;
  }

  // 初始化解码器上下文
  ret = avcodec_open2(audioCodecCtx, audioCodec, nullptr);
  if (ret < 0) {
    std::cout << "error: avcodec_open2() failed" << std::endl;
    goto fail;
  }

  AVChannelLayout outChLayout;
  AVChannelLayout inChLayout;
  av_channel_layout_default(&outChLayout, 2);
  av_channel_layout_default(&inChLayout, audioCodecCtx->ch_layout.nb_channels);

  // 创建音频重采样上下文
  ret = swr_alloc_set_opts2(&swrContext, &outChLayout, AV_SAMPLE_FMT_S16,
                            audioCodecCtx->sample_rate, &inChLayout,
                            audioCodecCtx->sample_fmt,
                            audioCodecCtx->sample_rate, 0, nullptr);
  if (ret < 0) {
    return -1;
  }

  swr_init(swrContext);

  // 创建 SDL_Surface
  SDL_Surface *screen =
      SDL_SetVideoMode(videoCodecCtx->width, videoCodecCtx->height, 0, 0);
  if (!screen) {
    std::cout << "error: SDL: could not set video mode" << std::endl;
    goto fail;
  }

  // YV12 SDL_Overlay
  sdlOverlay = SDL_CreateYUVOverlay(videoCodecCtx->width, videoCodecCtx->height,
                                    SDL_YV12_OVERLAY, screen);

  // 初始化 SDL Audio
  SDL_AudioSpec audioSpec;
  audioSpec.freq = audioCodecCtx->sample_rate;
  audioSpec.format = AUDIO_S16SYS;
  audioSpec.channels = 2;
  audioSpec.silence = 0;
  audioSpec.samples = 1024;
  audioSpec.callback = audioCallback;
  audioSpec.userdata = audioCodecCtx;
  if (SDL_OpenAudio(&audioSpec, nullptr) != 0) {
    std::cout << "error: SDL_OpenAudio() failed, " << SDL_GetError()
              << std::endl;
    goto fail;
  }

  SDL_PauseAudio(0);

  // 像素格式转换上下文
  SwsContext *swsCtx = sws_getContext(
      videoCodecCtx->width, videoCodecCtx->height, videoCodecCtx->pix_fmt,
      videoCodecCtx->width, videoCodecCtx->height, AV_PIX_FMT_YUV420P,
      SWS_BILINEAR, nullptr, nullptr, nullptr);

  // 视频帧
  frame = av_frame_alloc();
  if (!frame) {
    std::cout << "error: av_frame_alloc() failed" << std::endl;
    goto fail;
  }

  // 读取视频帧
  AVPacket packet;
  int frameFinished(0);
  int i(0);
  while (av_read_frame(formatCtx, &packet) >= 0) {
    if (packet.stream_index == videoStream) {
      // 解码视频帧
      ret = avcodec_send_packet(videoCodecCtx, &packet);
      if (ret < 0) {
        std::cout << "error: avcodec_send_packet() failed" << std::endl;
        goto fail;
      }
      ret = avcodec_receive_frame(videoCodecCtx, frame);
      if (ret == 0) {
        SDL_LockYUVOverlay(sdlOverlay);

        // YV12 和 YUV420P 格式区别，UV分量顺序不同
        // https://blog.csdn.net/dss875914213/article/details/120836765
        AVFrame frameDst;
        frameDst.data[0] = sdlOverlay->pixels[0];
        frameDst.data[1] = sdlOverlay->pixels[2];
        frameDst.data[2] = sdlOverlay->pixels[1];
        frameDst.linesize[0] = sdlOverlay->pitches[0];
        frameDst.linesize[1] = sdlOverlay->pitches[2];
        frameDst.linesize[2] = sdlOverlay->pitches[1];

        // 执行像素格式转换
        sws_scale(swsCtx, frame->data, frame->linesize, 0,
                  videoCodecCtx->height, frameDst.data, frameDst.linesize);

        SDL_UnlockYUVOverlay(sdlOverlay);

        SDL_Rect rect = {0, 0, (Uint16)videoCodecCtx->width,
                         (Uint16)videoCodecCtx->height};
        SDL_DisplayYUVOverlay(sdlOverlay, &rect);
      }
    } else if (packet.stream_index == audioStream) {
      AVPacket *copyPacket = av_packet_clone(&packet);
      std::unique_lock<std::mutex> lock(mutex);
      packets.push(copyPacket);
      lock.unlock();
      waitCondition.notify_all();
    }

    av_packet_unref(&packet);

    bool quit(false);

    SDL_Event event;
    SDL_PollEvent(&event);

    switch (event.type) {
    case SDL_QUIT:
      quit = true;
      break;

    default:
      break;
    }

    if (quit) {
      break;
    }
  }

fail:
  SDL_FreeYUVOverlay(sdlOverlay);
  SDL_Quit();
  swr_free(&swrContext);
  sws_freeContext(swsCtx);
  av_frame_free(&frame);
  avcodec_free_context(&audioCodecCtx);
  avcodec_free_context(&videoCodecCtx);
  avformat_close_input(&formatCtx);

  return 0;
}